package hci;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;

import javax.xml.stream.XMLStreamException;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import hci.utils.*;

/**
 * Handles image editing panel
 * 
 * @author Michal
 * 
 */
public class ImagePanel extends JPanel implements MouseListener,
MouseMotionListener, ActionListener {
	/**
	 * some java stuff to get rid of warnings
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * image to be tagged
	 */
	BufferedImage image = null;

	/**
	 * list of current polygon's vertices
	 */
	MyPolygon currentPolygon = null;

	/**
	 * list of polygons
	 */
	static ArrayList<MyPolygon> polygonsList = null;

	boolean dragging = false;
	Point currentlyDragging;
	Color currentColor;

	private boolean draggingPolygon;

	private MyPolygon currentlyDraggingPoly;

	private int xpress;

	private int ypress;

	private Timer timer;

	private static boolean deletingMode = false;

	private String imageURL;

	private boolean writingLabel = false;

	/**
	 * default constructor, sets up the window properties
	 */
	public ImagePanel() {

		currentPolygon = new MyPolygon();
		polygonsList = new ArrayList<MyPolygon>();

		this.setVisible(true);

		Dimension panelSize = new Dimension(800, 600);
		this.setSize(panelSize);
		this.setMinimumSize(panelSize);
		this.setPreferredSize(panelSize);
		this.setMaximumSize(panelSize);

		addMouseListener(this);
		addMouseMotionListener(this);
		timer = new Timer(50, this);

	}

	/**
	 * extended constructor - loads image to be labelled
	 * 
	 * @param imageName
	 *            - path to image
	 * @throws Exception
	 *             if error loading the image
	 */
	public ImagePanel(String imageName) throws Exception {
		this();

		imageURL = new String(imageName);
		image = ImageIO.read(new File(imageName));
		if (image.getWidth() > 800 || image.getHeight() > 600) {
			int newWidth = image.getWidth() > 800 ? 800
					: (image.getWidth() * 600) / image.getHeight();
			int newHeight = image.getHeight() > 600 ? 600
					: (image.getHeight() * 800) / image.getWidth();
			System.out.println("SCALING TO " + newWidth + "x" + newHeight);
			Image scaledImage = image.getScaledInstance(newWidth, newHeight,
					Image.SCALE_FAST);
			image = new BufferedImage(newWidth, newHeight,
					BufferedImage.TYPE_INT_RGB);
			image.getGraphics().drawImage(scaledImage, 0, 0, this);
		}
	}

	/**
	 * Displays the image
	 */
	public void ShowImage() {
		Graphics g = this.getGraphics();

		if (image != null) {
			g.drawImage(image, 0, 0, null);
		}
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);

		// display image
		ShowImage();

		if (deletingMode) {

			drawThickLine(g, 0, 0, this.getWidth(), 0, 10, Color.RED);
			drawThickLine(g, 0, this.getHeight(), this.getWidth(),
					this.getHeight(), 10, Color.RED);
			drawThickLine(g, 0, 0, 0, this.getHeight(), 10, Color.RED);
			drawThickLine(g, this.getWidth(), 0, this.getWidth(),
					this.getHeight(), 10, Color.RED);
		}

		// display all the completed polygons
		for (MyPolygon polygon : polygonsList) {

			drawPolygon(polygon);
			finishPolygon(polygon);
			if (polygon.isShowingLabel() && polygon.getLabel() != null) {

				FontRenderContext frc = ((Graphics2D) g).getFontRenderContext();
				String s = polygon.getLabel();
				Rectangle2D bounds = g.getFont().getStringBounds(s, frc);
				g.setColor(Color.WHITE);
				g.fillRect((int) polygon.getPolygon().getBounds().getCenterX()/*-(int) bounds.getWidth()*/,
						(int) polygon.getPolygon().getBounds().getCenterY()-(int) bounds.getHeight()+3,
						(int) bounds.getWidth(), (int) bounds.getHeight());
				g.setColor(Color.BLACK);
				g.drawString(polygon.getLabel(), (int) polygon.getPolygon()
						.getBounds().getCenterX(), (int) polygon.getPolygon()
						.getBounds().getCenterY());
			}

		}

		if (currentPolygon.isShowingLastLine()) {

			Point firstVertex = currentPolygon.getVertexs().get(0);
			Point lastVertex = currentPolygon.getVertexs().get(
					currentPolygon.getVertexs().size() - 1);

			Color translucent = new Color(currentPolygon.getColor().getRed(),
					currentPolygon.getColor().getGreen(), currentPolygon
					.getColor().getBlue(), 56);

			drawThickLine(g, firstVertex.getX(), firstVertex.getY(),
					lastVertex.getX(), lastVertex.getY(), 5, translucent);
		}

		// display current polygon
		drawPolygon(currentPolygon);
	}

	/**
	 * displays a polygon without last stroke
	 * 
	 * @param polygon
	 *            to be displayed
	 */
	public void drawPolygon(MyPolygon poly) {
		Graphics2D g = (Graphics2D) this.getGraphics();
		ArrayList<Point> polygon = poly.getVertexs();
		g.setColor(poly.getColor());
		for (int i = 0; i < polygon.size(); i++) {
			Point currentVertex = polygon.get(i);
			if (i != 0) {
				Point prevVertex = polygon.get(i - 1);
				g.drawLine(prevVertex.getX(), prevVertex.getY(),
						currentVertex.getX(), currentVertex.getY());
			}
			g.fillOval(currentVertex.getX() - 5, currentVertex.getY() - 5, 10,
					10);

		}
	}

	/**
	 * displays last stroke of the polygon (arch between the last and first
	 * vertices)
	 * 
	 * @param polygon
	 *            to be finished
	 */
	public void finishPolygon(MyPolygon poly) {
		// if there are less than 3 vertices than nothing to be completed
		ArrayList<Point> polygon = poly.getVertexs();
		if (polygon.size() >= 3) {
			Point firstVertex = polygon.get(0);
			Point lastVertex = polygon.get(polygon.size() - 1);

			Graphics2D g = (Graphics2D) this.getGraphics();
			g.setColor(poly.getColor());
			int[] xpoints = new int[polygon.size()];
			int[] ypoints = new int[polygon.size()];
			int i = 0;
			for (Point p : polygon) {

				xpoints[i] = p.getX();
				ypoints[i] = p.getY();
				i++;
			}
			g.drawLine(firstVertex.getX(), firstVertex.getY(),
					lastVertex.getX(), lastVertex.getY());
			Color translucent = new Color(poly.getColor().getRed(), poly
					.getColor().getGreen(), poly.getColor().getBlue(), 56);
			g.setColor(translucent);
			poly.setPolygon(new Polygon(xpoints, ypoints, polygon.size()));
			g.fill(poly.getPolygon());
		}
	}

	/**
	 * moves current polygon to the list of polygons and makes pace for a new
	 * one
	 */
	public void addNewPolygon() {
		// finish the current polygon if any
		if (currentPolygon != null) {
			finishPolygon(currentPolygon);
			polygonsList.add(currentPolygon);
		}

		currentPolygon = new MyPolygon();
	}

	boolean doubleClick;
	MouseEvent mouseEv;
	int coordx, coordy;

	public void mouseClicked(MouseEvent e) {
		// Reference:
		// http://stackoverflow.com/questions/548180/java-ignore-single-click-on-double-click
		
		if (!writingLabel) {
			coordx = e.getX();
			coordy = e.getY();
			mouseEv = e;
			// check if the cursors within image area
			if (coordx > image.getWidth() || coordy > image.getHeight()) {
				// if not do nothing
				return;
			}

			if (e.getClickCount() == 2) {
				doubleClickAct(mouseEv, coordx, coordy);
				doubleClick = true;
			} else {
				Integer timerinterval = (Integer) Toolkit.getDefaultToolkit()
				.getDesktopProperty("awt.multiClickInterval");
				timer = new Timer(timerinterval.intValue(),
						new ActionListener() {
					public void actionPerformed(ActionEvent evt) {
						if (doubleClick) {
							doubleClick = false; // reset flag
						} else {
							singleClickAct(mouseEv, coordx, coordy);
						}
					}
				});
				timer.setRepeats(false);
				timer.start();
			}
		}
	}

	private void doubleClickAct(MouseEvent e, int x, int y) {
		HciModifications.setModifications(true);
		for (final MyPolygon p : polygonsList) {

			if (p.getPolygon().contains(x, y)) {

				if (e.getButton() == MouseEvent.BUTTON1) {

					writingLabel = true;
					final JFrame frame = new JFrame("Label");
					frame.setUndecorated(true);
					frame.setAlwaysOnTop(true);
					final JTextField jtf = new JTextField();
					MaxLengthTextDocument maxLength = new MaxLengthTextDocument();
					maxLength.setMaxChars(15);
					jtf.setDocument(maxLength);
					jtf.setVisible(true);
					jtf.setText(p.getLabel());
					jtf.addActionListener(new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent arg0) {

							frame.setVisible(false);
							p.setLabel(jtf.getText());
							writingLabel = false;
							repaint();
						}

					});
					add(jtf);

					frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);

					frame.add(jtf);
					frame.setLocation(x, y);
					frame.setBounds(x, y, 100, 30);
					frame.setVisible(true);
					return;
				}
			}
		}
	}

	protected void singleClickAct(MouseEvent e, int x, int y) {
		HciModifications.setModifications(true);

		// check if the cursor is within a vertex

		for (MyPolygon a : polygonsList) {

			for (Point p : a.getVertexs()) {

				if (p.getX() < x + 6 && p.getX() > x - 6 && p.getY() < y + 6
						&& p.getY() > y - 6) {

					// we are clicking on an existing point
					if (e.getButton() != MouseEvent.BUTTON3)
						return;
				}
			}
		}

		for (Point p : currentPolygon.getVertexs()) {

			if (p.getX() < x + 6 && p.getX() > x - 6 && p.getY() < y + 6
					&& p.getY() > y - 6) {

				// we are clicking on an existing point
				if (e.getButton() != MouseEvent.BUTTON3)
					return;
			}
		}

		for (final MyPolygon p : polygonsList) {

			if (p.getPolygon().contains(x, y)) {

				if (e.getButton() == MouseEvent.BUTTON1 && deletingMode) {

					polygonsList.remove(p);
					if(p.getLabel() == null){
						ImageLabeller.messages.setText("Deleting Mode: ON - Polygon deleted ");
					} else {
						ImageLabeller.messages.setText("Deleting Mode: ON - Polygon "+p.getLabel() +" deleted ");
					}
					
					repaint();
					break;
				}
			}
		}
		if(!deletingMode){
			Graphics2D g = (Graphics2D) this.getGraphics();

			if (e.getButton() == MouseEvent.BUTTON1
					&& currentPolygon.isShowingLastLine()) {
				addNewPolygon();
			} else {

				// if the left button than we will add a vertex to poly
				if (e.getButton() == MouseEvent.BUTTON1) {

					System.out.println("adding vertex!!!" + e.getClickCount());
					g.setColor(currentPolygon.getColor());

					if (currentPolygon.getVertexs().size() != 0) {
						Point lastVertex = currentPolygon.getVertexs().get(
								currentPolygon.getVertexs().size() - 1);
						g.drawLine(lastVertex.getX(), lastVertex.getY(), x, y);
					}
					g.fillOval(x - 5, y - 5, 10, 10);
					Point p = new Point(x, y);
					p.setG2d((Graphics2D) g.create());
					currentPolygon.getVertexs().add(p);
					System.out.println(x + " " + y);
				}
			}
		}
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {

	}

	@Override
	public void mouseExited(MouseEvent arg0) {
	}

	@Override
	public void mousePressed(MouseEvent arg0) {

		HciModifications.setModifications(true);
		int x = arg0.getX();
		int y = arg0.getY();
		xpress = x;
		ypress = y;

		for (MyPolygon a : polygonsList) {

			for (Point p : a.getVertexs()) {

				if (p.getX() < x + 6 && p.getX() > x - 6 && p.getY() < y + 6
						&& p.getY() > y - 6) {
					// we are clicking on an existing point
					dragging = true;
					currentlyDragging = p;
					return;
				}
			}
		}

		for (Point p : currentPolygon.getVertexs()) {

			if (p.getX() < x + 6 && p.getX() > x - 6 && p.getY() < y + 6
					&& p.getY() > y - 6) {

				// we are clicking on an existing point
				dragging = true;
				currentlyDragging = p;
				return;
			}
		}

		// check if the cursor is within a polygon
		for (MyPolygon p : polygonsList) {

			if (p.getPolygon().contains(x, y)) {

				draggingPolygon = true;
				currentlyDraggingPoly = p;
				return;
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent arg0) {

		dragging = false;
		draggingPolygon = false;
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		HciModifications.setModifications(true);
		int x = e.getX();
		int y = e.getY();
		int diffx = x - xpress;
		int diffy = y - ypress;
		xpress = x;
		ypress = y;

		if (dragging) {

			currentlyDragging.setX(x);
			currentlyDragging.setY(y);
			repaint();
		}

		if (draggingPolygon) {

			ArrayList<Point> dp = currentlyDraggingPoly.getVertexs();
			for (Point p : dp) {

				int px = p.getX();
				int py = p.getY();
				p.setX((int) (px + diffx));
				p.setY((int) (py + diffy));
				repaint();
			}
		}
	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
		if (!ImageLabeller.viewLabels) {
			int x = arg0.getX();
			int y = arg0.getY();

			if (currentPolygon != null) {

				if (currentPolygon.getVertexs().size() > 2) {

					Point firstVertex = currentPolygon.getVertexs().get(0);
					Point lastVertex = currentPolygon.getVertexs().get(
							currentPolygon.getVertexs().size() - 1);
					Polygon p = getLineArea(firstVertex, lastVertex);

					if (p.contains(x, y)) {

						currentPolygon.setShowingLastLine(true);
						repaint();

					} else if (!p.contains(x, y)) {

						currentPolygon.setShowingLastLine(false);
						repaint();
					}
				}
			}

			for (MyPolygon p: polygonsList){

				if(p.getLabel()!=null){
					if(p.getPolygon().contains(x,y)){
						p.setShowingLabel(true);
					} else {
						p.setShowingLabel(false);
					}
					repaint();
				}
			}		
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		timer.stop();
	}

	public void saveFile(String filename) {
		try {
			System.out.println(imageURL);
			HciPersistence.SaveFile(polygonsList, filename, imageURL);
			HciModifications.setModifications(false);
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void deletingMode() {
		// TODO Auto-generated method stub
		deletingMode = !deletingMode;
		repaint();
	}

	public String getDeleltingMode() {
		if (deletingMode)
			return "OFF";
		else
			return "ON";
	}

	public void drawThickLine(Graphics g, int x1, int y1, int x2, int y2,
			int thickness, Color c) {
		// Reference: http://www.rgagnon.com/javadetails/java-0260.html

		// The thick line is in fact a filled polygon
		g.setColor(c);
		int dX = x2 - x1;
		int dY = y2 - y1;
		// line length
		double lineLength = Math.sqrt(dX * dX + dY * dY);

		double scale = (double) (thickness) / (2 * lineLength);

		// The x,y increments from an endpoint needed to create a rectangle...
		double ddx = -scale * (double) dY;
		double ddy = scale * (double) dX;
		ddx += (ddx > 0) ? 0.5 : -0.5;
		ddy += (ddy > 0) ? 0.5 : -0.5;
		int dx = (int) ddx;
		int dy = (int) ddy;

		// Now we can compute the corner points...
		int xPoints[] = new int[4];
		int yPoints[] = new int[4];

		xPoints[0] = x1 + dx;
		yPoints[0] = y1 + dy;
		xPoints[1] = x1 - dx;
		yPoints[1] = y1 - dy;
		xPoints[2] = x2 - dx;
		yPoints[2] = y2 - dy;
		xPoints[3] = x2 + dx;
		yPoints[3] = y2 + dy;

		g.fillPolygon(xPoints, yPoints, 4);
	}

	public Polygon getLineArea(Point a, Point b) {

		Polygon p;

		int dX = b.getX() - a.getX();
		int dY = b.getY() - b.getY();
		// line length
		double lineLength = Math.sqrt(dX * dX + dY * dY);

		double scale = (double) (27) / (2 * lineLength);

		// The x,y increments from an endpoint needed to create a rectangle...
		double ddx = -scale * (double) dY;
		double ddy = scale * (double) dX;
		ddx += (ddx > 0) ? 0.5 : -0.5;
		ddy += (ddy > 0) ? 0.5 : -0.5;
		int dx = (int) ddx;
		int dy = (int) ddy;

		// Now we can compute the corner points...
		int xPoints[] = new int[4];
		int yPoints[] = new int[4];

		xPoints[0] = a.getX() + dx;
		yPoints[0] = a.getY() + dy;
		xPoints[1] = a.getX() - dx;
		yPoints[1] = a.getY() - dy;
		xPoints[2] = b.getX() - dx;
		yPoints[2] = b.getY() - dy;
		xPoints[3] = b.getX() + dx;
		yPoints[3] = b.getY() + dy;

		p = new Polygon(xPoints, yPoints, xPoints.length);

		return p;
	}
}
